# _*_coding:utf-8_*_
# 作者    ：SunDuWei
# 创作时间 ：2020-02-18
# 此函数主要用于期货数据生成包括因子数据
import talib
import pandas as pd
import json2config as j2c
import datetime
import numpy as np
import time


class future_x1(object):

    @classmethod
    def macd(cls, df, **kwargs):
        # 计算macd
        df['x1_diff'], df['x1_dea'], df['x1_macd'] = talib.MACD(df['close'],
                                                                fastperiod=kwargs['param_fast'],
                                                                slowperiod=kwargs['param_slow'],
                                                                signalperiod=kwargs['param_ema'])
        return df

    @classmethod
    def rsi(cls, df, **kwargs):
        # 计算rsi
        df['x1_rsi'] = talib.RSI(df['close'], timeperiod=kwargs['param_rs'])
        return df

    @classmethod
    def kdj(cls, df, **kwargs):
        # 计算kdj
        df['x1_slowK'], df['x1_slowD'] = talib.STOCH(df['high'], df['low'], df['close'],
                                                     fastk_period=kwargs['param_fastk'],
                                                     slowk_period=kwargs['param_slowk'],
                                                     slowk_matype=kwargs['param_slowk_ma'],
                                                     slowd_period=kwargs['param_slowd'],
                                                     slowd_matype=kwargs['param_slowd_ma'])
        return df

    @classmethod
    def boll(cls, df, **kwargs):
        # 计算布林带
        df['x1_upperline'], df['x1_middleline'], df['x1_lowerline'] = talib.BBANDS(df['close'],
                                                                                   timeperiod=kwargs['param_ma'],
                                                                                   nbdevup=kwargs['param_stdup'],
                                                                                   nbdevdn=kwargs['param_stddown'],
                                                                                   matype=0)
        return df

    @classmethod
    def obv(cls, df):
        df['x1_OBV'] = talib.OBV(df['close'], df['volume'])
        return df

    @classmethod
    def adocs(cls, df):
        df['x1_adocs'] = talib.ADOSC(df['high'], df['low'], df['close'], df['volume'], fastperiod=3,
                              slowperiod=10)
        return df


class future_y1(object):

    # 生成收益率y1类因子
    @classmethod
    def day_yield(cls, df, **kwargs):
        for values in kwargs.values():
            df['y1_%sdays_yield' % values] = df['close'].shift(values) - df['close']
        return df

    @classmethod
    def min_yield(cls, df, **kwargs):
        nmin = kwargs['nmin']
        for values, keys in zip(kwargs.values(), kwargs.keys()):
            if values != 'close_yield' and keys != 'nmin':
                nshift = int(values / nmin)
                df['y1_%smin_yield' % values] = df['close'].shift(-nshift) - df['close']
            elif values != nmin:
                close_time = datetime.datetime.strptime('15:00:00', '%H:%M:%S').time()
                df1 = df[df['time'] == close_time]['close']
                df = pd.merge(df, df1, on='datetime', how='outer')
                df.fillna(method='bfill', inplace=True)
                df.rename(columns={'close_y': 'y1_close_day'}, inplace=True)
                df.rename(columns={'close_x': 'close'}, inplace=True)
                df['y1_close_yield'] = df['y1_close_day'] - df['close']

        return df


class future_x2(object):
    # 生成突破类因子
    @classmethod
    def x2_box_data(cls, df):
        df.reset_index(drop=True, inplace=True)
        df['time'] = df['time'].apply(str)
        df['date'] = df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
        df['x2_close_std'] = df['close'].rolling(window=30).std()
        df['x2_delta_volatility'] = (df['x2_close_std'] - df['x2_close_std'].shift(1)) / df['x2_close_std']
        df['x2_params_volatility'] = 20
        df['x2_middleline'] = np.nan
        df['x2_band'] = np.nan
        df['x2_high_point'] = np.nan
        df['x2_low_point'] = np.nan
        time_start = time.time()
        for i in range(len(df)):
            if i > 60:
                df.loc[i, 'x2_params_volatility'] = min(max(round(df.loc[i - 1, 'x2_params_volatility'] * (1 + df.loc[i, 'x2_delta_volatility'])), 20), 60)
                lookBackDays = df.loc[i, 'x2_params_volatility']
                df.loc[i, 'x2_middleline'] = df.loc[i - lookBackDays: i - 1, 'close'].mean()
                df.loc[i, 'x2_band'] = df.loc[i - lookBackDays + 1: i, 'close'].std()
                df.loc[i, 'x2_high_point'] = df.loc[i - lookBackDays: i - 1, 'high'].max()
                df.loc[i, 'x2_low_point'] = df.loc[i - lookBackDays: i - 1, 'low'].min()
        time_end = time.time()
        time_c = time_end - time_start
        print(time_c)
        df['x2_upperline'] = df['x2_middleline'] + 2 * df['x2_band']
        df['x2_lowerline'] = df['x2_middleline'] - 2 * df['x2_band']

        return df
    # 生成价崩类因子
    @classmethod
    def x2_price_collapse(cls, df):
        df.reset_index(drop=True, inplace=True)
        df['time'] = df['time'].apply(str)
        df['date'] = df['date'].apply(lambda x: x.strftime('%Y-%m-%d'))
        df['x2_collapse_price'] = df['close'].shift(1) - 30
        return df

    @classmethod
    def price_collapse(cls, df):
        df['x2_collapse_signal'] = df.apply(lambda x: 1 if x.close <= x.x2_collapse_price else 0)
        return df

    @classmethod
    def break_data_choose(cls, df):
        def isbreak(x, y1, y2, z1, z2):
            if np.isnan(x) or np.isnan(y1) or np.isnan(z1):
                return 0
            elif x > y1 and x > y2:
                return 1
            elif x < z1 and x < z2:
                return -1
            else:
                return 0

        df['x2_break_signal'] = df.apply(lambda
         x: isbreak(x.close, x.x2_upperline, x.x2_high_point, x.x2_lowerline, x.x2_lowerline), axis=1)
        # df.dropna(axis=0, how='any', inplace=True)
        return df


class future_x3(object):
    # 生成x3类基差因子

    @classmethod
    def future_now_price(cls, df, data_kind):
        data_path = r'E:\课堂资料\future_data\品种_现期表\%s.xlsx' % data_kind
        df_current = pd.read_excel(data_path)
        df['time'] = df['time'].apply(str)
        df1 = df.loc[df['time'] == '21:05:00']
        df1.fillna(0, inplace=True)
        df_current['日期'] = df_current['日期'].apply(lambda x: x.replace(" ", ''))
        df2 = pd.merge(df1, df_current, how='outer', left_on='date', right_on='日期')
        df2.dropna(how='any', inplace=True)
        df2['x3_近月和主力合约基差'] = df2['近月价格'] - df2['主力价格']
        df2['x3_近月和主力合约基差百分比'] = df2['x3_近月和主力合约基差'] / df2['近月价格']
        df2['基差2_2'] = df2['基差2_2'].astype(str).apply(lambda x: x.replace('%', ''))
        df2['基差2_2'] = pd.to_numeric(df2['基差2_2'])
        df2['基差2_2'] = df2['基差2_2'] / 100
        df2['基差1_2'] = df2['基差1_2'].astype(str).apply(lambda x: x.replace('%', ''))
        df2['基差1_2'] = pd.to_numeric(df2['基差1_2'])
        df2['基差1_2'] = df2['基差1_2'] / 100
        df2.rename(columns={
            '现货价格': 'x3_现货价格',
            '近月价格': 'x3_近月价格',
            '基差1_1': 'x3_现货和近月基差',
            '基差1_2': 'x3_现货和近月基差百分比',
            '主力价格': 'x3_主力价格',
            '基差2_1': 'x3_现货和主力基差',
            '基差2_2': 'x3_现货和主力基差百分比'}, inplace=True
        )
        df2 = df2[['date', 'time', 'x3_现货价格',
                   'x3_近月价格', 'x3_现货和近月基差', 'x3_现货和近月基差百分比',
                   'x3_主力价格', 'x3_现货和主力基差', 'x3_现货和主力基差百分比',
                   'x3_近月和主力合约基差', 'x3_近月和主力合约基差百分比']]
        df3 = pd.merge(df, df2, on=['time', 'date'], how='outer')
        df3.fillna(method='ffill', inplace=True)
        return df3

    @classmethod
    def high_low_diff(cls, df):
        df['x3_high_diff'] = df['close'] - df['close'].rolling(window=120).max()
        df['x3_low_diff'] = df['close'] - df['close'].rolling(window=120).min()
        return df


class future_x4(object):
    @classmethod
    def index_factor(cls, df, cn_plate):
        df_index = pd.read_excel(r'E:\课堂资料\future_data\板块指数\index_%s.xlsx' % cn_plate)
        df_index.rename(columns={'close': 'x4_index_close',
                                 'vol': 'x4_index_volume',
                                 'amount': 'x4_index_amount',
                                 'trade_date': 'date'
                                 }, inplace=True)
        df_index['x4_3days_close'] = df_index['x4_index_close'] - df_index['x4_index_close'].shift(-2)
        df_index['x4_5days_close'] = df_index['x4_index_close'] - df_index['x4_index_close'].shift(-4)
        df_index['x4_12days_close'] = df_index['x4_index_close'] - df_index['x4_index_close'].shift(-11)
        df_index['x4_index_volume_diff'] = df_index['x4_index_volume'] - df_index['x4_index_volume'].shift(-1)
        df_index['x4_index_volume_diff_precent'] = df_index['x4_index_volume_diff'] / df_index['x4_index_volume'].shift(
            -1)
        df_index.drop(['ts_code', 'open', 'high', 'low', 'pre_close', 'change', 'pct_chg'], axis=1, inplace=True)
        df1 = df.loc[df['time'] == '21:05:00']
        df1 = pd.merge(df1, df_index, on='date')
        df1 = df1[['date', 'time', 'x4_index_close', 'x4_index_volume',
                   'x4_3days_close', 'x4_5days_close',
                   'x4_12days_close', 'x4_index_volume_diff',
                   'x4_index_volume_diff_precent']
        ]
        df = pd.merge(df, df1, on=['date', 'time'], how='outer')
        df.fillna(method='ffill', inplace=True)

        return df

    @classmethod
    def relate_kind(cls, df, relate_kind, min_kind):
        df_relate = pd.read_excel(
            r'E:\课堂资料\future_data\%s\%s_all_%s_data%s.xlsx' % (relate_kind, relate_kind, min_kind, ''))

        df_relate['x4_relate_kind_3klines_change'] = df_relate['close'] - df_relate['close'].shift(2)
        df_relate['x4_relate_kind_5klines_change'] = df_relate['close'] - df_relate['close'].shift(4)
        df_relate['x4_relate_kind_12klines_change'] = df_relate['close'] - df_relate['close'].shift(11)
        df_relate['time'] = df_relate['time'].apply(str)
        df_relate['date'] = df_relate['date'].apply(lambda x: x.strftime("%Y-%m-%d"))
        df_relate.rename(columns={'close': 'x4_relate_kind_close'}, inplace=True)
        df_relate['x4_vol_change_num'] = df_relate['volume'] - df_relate['volume'].shift(1)
        df_relate['x4_vol_change_precent'] = df_relate['x4_vol_change_num'] / df_relate['volume'].shift(1)
        df_relate['x4_position_change_num'] = df_relate['position'] - df_relate['position'].shift(1)
        df_relate['x4_position_change_precent'] = df_relate['x4_position_change_num'] / df_relate['position'].shift(1)
        df_relate = df_relate[['date', 'time', 'x4_relate_kind_3klines_change',
                               'x4_relate_kind_5klines_change', 'x4_relate_kind_12klines_change',
                               'x4_vol_change_num', 'x4_vol_change_precent', 'x4_position_change_num',
                               'x4_position_change_precent']]
        df = pd.merge(df, df_relate, on=['date', 'time'], how='outer')
        return df


class future_x5(object):
    # 生成x5类动量因子
    @classmethod
    def mommentum(cls, df):
        df['x5_vol_change_num'] = df['volume'] - df['volume'].shift(1)
        df['x5_vol_change_precent'] = df['x5_vol_change_num'] / df['volume']
        df['x5_position_change_num'] = df['position'] - df['position'].shift(1)
        df['x5_position_change_precent'] = df['x5_position_change_num'] / df['position']
        df['x5_mul_position_volume'] = df['volume'] * df['position']
        return df


class future_y3(object):
    @classmethod
    def strategy_yield(cls, df):
        df['y3_strategy_yield'] = 0
        df['y3_strategy_time'] = 0
        for i in range(len(df)):
            if df.loc[i, 'x2_break_signal'] != 0:
                nIsPosition = df.loc[i, 'x2_break_signal']
                nopprice = df.loc[i, 'close']
                code_name = df.loc[i, 'close']
                for j in range(i + 1, len(df)):
                    try:
                        if (nIsPosition == 1 and df.loc[j, 'close'] <= df.loc[j, 'x2_middleline']) or code_name != df.loc[j + 1, 'code']:
                            # 多头平仓信号
                            df.loc[i, 'y3_strategy_yield'] = df.loc[j, 'close'] - nopprice
                            df.loc[i, 'y3_strategy_time'] = df.loc[j, 'date'] + ' ' + df.loc[j, 'time']
                            break
                        elif (nIsPosition == -1 and df.loc[j, 'close'] >= df.loc[j, 'x2_middleline']) or code_name != df.loc[j + 1, 'code']:
                            # 空头平仓信号
                            df.loc[i, 'y3_strategy_yield'] = df.loc[j, 'close'] - nopprice
                            df.loc[i, 'y3_strategy_time'] = df.loc[j, 'date'] + ' ' + df.loc[j, 'time']
                            break
                    except:
                        df.loc[i, 'y3_strategy_yield'] = df.loc[j, 'close'] - nopprice
                        df.loc[i, 'y3_strategy_time'] = df.loc[j, 'date'] + ' ' + df.loc[j, 'time']

        return df


class future_x6(object):
    @classmethod
    def period_tech_index(cls, df, kind, min_period, is_day=False):
        df_period_io = r'E:\课堂资料\future_data\%s\%s_all_%s_data.xlsx' % (kind, kind, min_period)
        if is_day:
            df_period = pd.read_excel(df_period_io)
        else:
            df_period = pd.read_excel(df_period_io, index_col='datetime')
        df_period['date'] = df_period['date'].apply(lambda k: k.strftime('%Y-%m-%d'))
        if not is_day:
            df_period['time'] = df_period['time'].apply(str)
        df_period['x6_%s_diff' % min_period], df_period['x6_%s_dea' % min_period], df_period['x6_%s_macd' % min_period] =\
            talib.MACD(df_period['close'], fastperiod=12, slowperiod=26, signalperiod=9)
        df_period['x6_%s_rsi' % min_period] = talib.RSI(df_period['close'], timeperiod=12)
        df_period['x6_%s_slowK' % min_period], df_period['x6_%s_slowD' % min_period] =\
            talib.STOCH(df_period['high'], df_period['low'], df_period['close'],
                        fastk_period=9, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0)
        df_period['x6_%s_upperline' % min_period], df_period['x6_%s_middleline' % min_period], df_period['x6_%s_lowerline' % min_period] \
            = talib.BBANDS(df_period['close'], timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
        df_period['x6_%s_OBV' % min_period] = talib.OBV(df_period['close'], df_period['volume'])
        df_period['x6_%s_adocs' % min_period] = talib.ADOSC(df_period['high'], df_period['low'], df_period['close'],
                                                            df_period['volume'], fastperiod=3, slowperiod=10)

        df_period['x6_%s_position' % min_period] = df_period['position']
        df_period['x6_%s_position_change' % min_period] = df_period['position'] - df_period['position'].shift(1)
        ser_col = pd.Series(df_period.columns)
        x6_columns = list(ser_col[ser_col.str.startswith('x6_')])
        if is_day:
            x6_columns_all = x6_columns + ['date']
        else:
            x6_columns_all = x6_columns + ['date'] + ['time']
        df_period = df_period[x6_columns_all]
        if is_day:
            df_temp = df[df['time'] == '21:05:00']
            df_temp1 = pd.merge(df_temp, df_period, on='date', how='inner')
            x6_columns_all = x6_columns_all + ['time']
            df_temp2 = df_temp1[x6_columns_all]
            df = pd.merge(df, df_temp2, on=['date', 'time'], how='outer')
            df.fillna(method='ffill', inplace=True)
        else:
            df = pd.merge(df, df_period, on=['date', 'time'], how='outer')
            df.fillna(method='ffill', inplace=True)
        return df


if __name__ == '__main__':
    # 公共参数
    path = r'E:\课堂资料\future\config.json'
    data_io = r'E:\课堂资料\future_data\%s\%s_all_%s_data%s.xlsx'
    kind_min = 'min30'
    macd_param, kdj_param, rsi_param, boll_param = j2c.x1_factor_config(path)
    y1_param_day, y1_param_min = j2c.y1_factor_config(path)
    df_mul_param = pd.read_excel(
        r'E:\课堂资料\future_data\multiple_kind_factor_sheet_index.xlsx')
    df_all = pd.DataFrame()
    # 是否单品种分开测试
    nIs_Solo_Kind = True
    for i in range(len(df_mul_param)):
        if not nIs_Solo_Kind:
            # 品种参数
            kind = df_mul_param.loc[i, 'kind']
            one_hot = df_mul_param.loc[i, 'one_hot']
            relate_kind = df_mul_param.loc[i, 'relate_kind']
            cn_plate = df_mul_param.loc[i, 'cn_plate']
            day_main_io = data_io % (kind, kind, 'day', '')
            day_second_io = data_io % (kind, kind, 'day', '_second')
            day_min_io = data_io % (kind, kind, kind_min, '')
            # 数据读取
            df_main = pd.read_excel(day_main_io)
            df_second = pd.read_excel(day_second_io)
            df_min = pd.read_excel(day_min_io, index_col='datetime')
            df_min = future_y1.min_yield(df_min, **y1_param_min)
            df_min = future_x1.rsi(df_min, **rsi_param)
            df_min = future_x1.boll(df_min, **boll_param)
            df_min = future_x1.kdj(df_min, **kdj_param)
            df_min = future_x1.macd(df_min, **macd_param)
            df_min = future_x1.obv(df_min)
            df_min = future_x1.adocs(df_min)
            df_min = future_x2.x2_box_data(df_min)
            df_min = future_x2.break_data_choose(df_min)
            df_min = future_x3.future_now_price(df_min, kind)
            df_min = future_x3.high_low_diff(df_min)
            # df_min = future_x4.index_factor(df_min, cn_plate)
            df_min = future_x4.relate_kind(df_min, relate_kind, kind_min)
            df_min = future_x5.mommentum(df_min)
            df_min = future_y3.strategy_yield(df_min)
            df_min['kind'] = one_hot
            df_min = future_x6.period_tech_index(df_min, kind, 'min30', is_day=False)
            # df_min = future_x6.period_tech_index(df_min, kind, 'min15', is_day=False)
            df_min = future_x6.period_tech_index(df_min, kind, 'day', is_day=True)
            df_all = df_all.append(df_min, ignore_index=True)
            print('已完成第%d个品种的因子库，一共有%d个品种' % (i + 1, len(df_mul_param)))
        else:
            # 品种参数
            kind = df_mul_param.loc[i, 'kind']
            one_hot = df_mul_param.loc[i, 'one_hot']
            relate_kind = df_mul_param.loc[i, 'relate_kind']
            cn_plate = df_mul_param.loc[i, 'cn_plate']
            day_main_io = data_io % (kind, kind, 'day', '')
            day_second_io = data_io % (kind, kind, 'day', '_second')
            day_min_io = data_io % (kind, kind, kind_min, '')
            # 数据读取
            df_main = pd.read_excel(day_main_io)
            df_second = pd.read_excel(day_second_io)
            df_min = pd.read_excel(day_min_io, index_col='datetime')
            df_min = future_y1.min_yield(df_min, **y1_param_min)
            df_min = future_x1.rsi(df_min, **rsi_param)
            df_min = future_x1.boll(df_min, **boll_param)
            df_min = future_x1.kdj(df_min, **kdj_param)
            df_min = future_x1.macd(df_min, **macd_param)
            df_min = future_x1.obv(df_min)
            df_min = future_x1.adocs(df_min)
            df_min = future_x2.x2_box_data(df_min)
            df_min = future_x2.break_data_choose(df_min)
            # df_min = future_x3.future_now_price(df_min, kind)
            df_min = future_x3.high_low_diff(df_min)
            # df_min = future_x4.index_factor(df_min, cn_plate)
            df_min = future_x4.relate_kind(df_min, relate_kind, kind_min)
            df_min = future_x5.mommentum(df_min)
            df_min = future_y3.strategy_yield(df_min)
            df_min['kind'] = one_hot
            # df_min = future_x6.period_tech_index(df_min, kind, 'min15', is_day=False)
            # df_min = future_x6.period_tech_index(df_min, kind, 'min30', is_day=False)
            df_min = future_x6.period_tech_index(df_min, kind, 'day', is_day=True)
            print('已完成第%d个品种的因子库，一共有%d个品种' % (i + 1, len(df_mul_param)))
            df_min.to_excel(r'E:\课堂资料\future_data\因子库\%s\factor_sheet_all.xlsx' % kind, index=False)
    if not nIs_Solo_Kind:
        print('正在写入xlsx中...')
        df_all.to_excel(r'E:\课堂资料\future_data\因子库\factor_sheet_all.xlsx', index=False)
